package com.cocolover2.andbase.http.api;

import android.support.v4.util.ArrayMap;

import com.cocolover2.andbase.http.IResponseResult;
import com.cocolover2.andbase.http.OnHttpListener;
import com.cocolover2.andbase.http.response.ProgressResponseBody;
import com.cocolover2.andbase.utils.log.KLog;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.util.Map;

import io.reactivex.Observable;
import io.reactivex.ObservableSource;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.annotations.NonNull;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;

/**
 * 基本接口帮助类<br>
 * 适用retrofit2+okhttp+rxAndroid<br>
 * 1.支持返回{@link ResponseBody}
 * 2.支持返回实现接口{@link IResponseResult}
 *
 * @param <A> ApiService
 * @since 2.1.0 该类更新为rxjava2.x系列并对结构进行重新优化，更加灵活，扩展
 */
@SuppressWarnings("all")
public abstract class ABaseApi<A> {
    final String TAG = "ABaseApi";
    private String mBaseUrl = "/";
    /**
     * 默认的ApiService接口类
     */
    private Class<A> mDefaultClz;
    /**
     * key:APi接口类的全路径
     * value:Api接口类的缓存实例对象
     */
    private ArrayMap<String, Object> mApiServiceMap = new ArrayMap<>();

    /**
     * 构造方法
     *
     * @param context 上下文
     * @param baseUrl 基本路径
     * @param clz     api接口类
     * @since v2.0.4
     */
    public ABaseApi(String baseUrl, Class<A> defaultClz) {
        mDefaultClz = defaultClz;
        mBaseUrl = baseUrl;
    }

    private Gson getGson() {
        return new GsonBuilder()
                // 设置日期时间格式，另有2个重载方法
                .setDateFormat("yyyy-MM-dd hh:mm:ss")
                // 禁此序列化内部类
                .disableInnerClassSerialization()
                .disableHtmlEscaping() //禁止转义html标签
                .serializeNulls()//序列化null
                .setPrettyPrinting()//格式化输出
                .create();
    }

    /**
     * 构建 ApiService
     *
     * @param baseUrl 项目基本路径
     * @param clz     APi接口类
     * @param client  okhttpclient
     * @param <T>     APi接口类
     * @return APi接口类实例
     */
    public <T> T buildApiService(String baseUrl, Class<T> clz, OkHttpClient client) {
        T service = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create(getGson()))
                .client(client)
                //这里可以添加其他配置
                .build().create(clz);
        KLog.d("当前使用的api接口使用的是=======>" + clz.getName());
        //缓存生成的apiservice接口对象
        cacheApiServiceObj(clz.getName(), service);
        return service;
    }

    /**
     * 缓存Api请求接口服务
     *
     * @param key     关键字
     * @param service api接口请求服务实例
     * @param <T>     Api接口请求类型
     */
    private <T> void cacheApiServiceObj(String key, T service) {
        if (mApiServiceMap == null) {
            mApiServiceMap = new ArrayMap<>();
        }
        if (!mApiServiceMap.containsKey(key)) {
            mApiServiceMap.put(key, service);
        }
    }

    /**
     * 根据api接口类获取特定Api请求接口服务对象
     *
     * @param clz apiservice接口类
     * @param <T> Api接口请求类型
     * @return 返回特定Api请求接口服务对象
     */
    public <T> T getApiService(Class<T> clz) {
        if (mApiServiceMap != null && mApiServiceMap.containsKey(clz.getName())) {
            return (T) mApiServiceMap.get(clz.getName());
        } else {
            return buildApiService(mBaseUrl, clz, buildOkhttpClient());
        }
    }

    /**
     * 获取默认的Api请求接口服务对象
     */
    public A getApiService() {
        return getApiService(mDefaultClz);
    }

    /**
     * rxjava 异常消费者分发请求异常
     */
    private Consumer<Throwable> getThrowableConsumer(final OnHttpListener listener) {
        return new Consumer<Throwable>() {
            @Override
            public void accept(Throwable throwable) throws Exception {
                handleErrorException(throwable, listener);
            }
        };
    }

    /**
     * 处理请求成功的响应结果
     *
     * @param listener 请求回调
     * @param <T>      IResponseResult类
     * @return
     */
    private <T> Consumer<T> getNextConsumer(final OnHttpListener<T> listener) {
        return new Consumer<T>() {
            @Override
            public void accept(T t) throws Exception {
                if (listener != null) {
                    if (t instanceof IResponseResult) {
                        IResponseResult result = (IResponseResult) t;
                        if (isResponseSuccess(result.getCode())) {//是否请求成功
                            listener.onSuccess(t);
                        } else {
                            handleCustomizeException(result.getCode(), result.getMessage(), listener);
                        }
                    } else if (t instanceof ResponseBody) {
                        handleResponseBodyResult((ResponseBody) t, listener);
                    }
                }
            }
        };
    }

    /**
     * 处理 responseBody请求返回结果
     * <p>
     * 当结果不是json格式时，请求结果回调给{@link OnHttpListener#onSuccess(ResponseBody)}
     * </p>
     *
     * @param t
     * @param listener
     * @param <T>
     * @throws JSONException
     * @throws IOException
     */
    private <T> void handleResponseBodyResult(ResponseBody t, OnHttpListener<T> listener) throws IOException {
        //正常json格式数据
        String respStr = t.string();
        try {
            JSONObject object = new JSONObject(respStr);
            int errCode = object.getInt(getErrCodeKey());
            if (isResponseSuccess(errCode)) {
                listener.onSuccess((T) ResponseBody.create(t.contentType(), object.toString()));
            } else {
                String errMsg = object.getString(getErrMsgKey());
                handleCustomizeException(errCode, errMsg, listener);
            }
        } catch (JSONException e) {
            //不是json格式数据
            listener.onSuccess((T) ResponseBody.create(t.contentType(), respStr));
        }
    }

    /**
     * 异步处理网络请求
     *
     * @param obserableListener 请求回调
     * @param listener          响应回调
     * @param <T>               {@link IResponseResult}类型的实体类才能触发异常重试机制
     * @return
     */
    public <T> Disposable sendRequest(OnObservableListener<T> obserableListener, final OnHttpListener<T> listener) {
        return liftObservable(obserableListener).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(getNextConsumer(listener), getThrowableConsumer(listener));
    }

    /**
     * 同步处理网络请求
     *
     * @param obserableListener 请求回调
     * @param listener          响应回调
     * @param <T>               {@link IResponseResult}类型的实体类才能触发异常重试机制
     * @return
     */
    public <T> Disposable sendRequestSync(OnObservableListener<T> obserableListener, final OnHttpListener<T> listener) {
        return liftObservable(obserableListener).subscribe(getNextConsumer(listener), getThrowableConsumer(listener));
    }


    /**
     * 变换原始的observable 使其支持在异常时可以重新执行
     *
     * @param listener 获取原始的observable
     * @param <T>      任意类型
     * @return
     */
    public <T> Observable<T> liftObservable(final OnObservableListener<T> listener) {
        return Observable.just("").flatMap(new Function<String, ObservableSource<T>>() {
            @Override
            public ObservableSource<T> apply(@NonNull String s) throws Exception {
                Observable<T> observable = listener.onObservable();
                return observable.flatMap(new Function<T, ObservableSource<T>>() {
                    @Override
                    public ObservableSource<T> apply(@NonNull T t) throws Exception {
                        if (t instanceof IResponseResult) {
                            Exception exception = retryWhenException(((IResponseResult) t).getCode());
                            if (exception != null) {
                                return Observable.error(exception);
                            } else {
                                return Observable.just(t);
                            }
                        } else if (t instanceof ResponseBody) {
                            String respStr = ((ResponseBody) t).string();
                            try {
                                JSONObject object = new JSONObject(respStr);
                                if (object.has(getErrCodeKey())) {
                                    Exception exception = retryWhenException(object.getInt(getErrCodeKey()));
                                    if (exception != null) {
                                        return Observable.error(exception);
                                    } else {
                                        return Observable.just((T) ResponseBody.create(((ResponseBody) t).contentType(), respStr));
                                    }
                                } else {
                                    return Observable.just((T) ResponseBody.create(((ResponseBody) t).contentType(), respStr));
                                }
                            } catch (JSONException e) {
                                return Observable.just((T) ResponseBody.create(((ResponseBody) t).contentType(), respStr));
                            }
                        } else {
                            return Observable.just(t);
                        }
                    }
                });
            }
        }).retryWhen(new RetryWhenFactor());
    }

    /**
     * 异常重试的工厂类
     */
    private class RetryWhenFactor implements Function<Observable<? extends Throwable>, Observable<?>> {
        int tryCount = 0;

        @Override
        public Observable<?> apply(@NonNull Observable<? extends Throwable> observable) throws Exception {
            return observable.flatMap(new Function<Throwable, ObservableSource<?>>() {
                @Override
                public ObservableSource<?> apply(@NonNull Throwable throwable) throws Exception {
                    Observable retryWhenObservable = retryWhenObservable(throwable, ++tryCount);
                    if (retryWhenObservable == null) {
                        return Observable.error(throwable);
                    } else {
                        return retryWhenObservable;
                    }
                }
            });
        }
    }

    /**
     * 构建okhttpClient
     *
     * @return okhttpClient
     */
    public abstract
    @android.support.annotation.NonNull
    OkHttpClient buildOkhttpClient();

    /**
     * 返回网络请求是否成功的判断结果<br>
     * 只是作用于实现{@link IResponseResult}接口的实体类(bean)
     */
    protected abstract boolean isResponseSuccess(int code);

    /**
     * 定义哪些异常可以重新请求
     * <p>
     * 1.响应结果实现{@link IResponseResult}接口时必回调该方法<br>
     * 2.响应结果为{@link ResponseBody}时，返回数据为json且包含{@link #getErrCodeKey()}关键字才会回调该方法
     * </p>
     *
     * @param code 错误码
     * @return 返回 null 时不重新请求
     */
    protected abstract Exception retryWhenException(int code);

    /**
     * 重试条件的获取(例如token失效造成请求失败，重新刷新token再次请求尝试)
     *
     * @param throwable 异常
     * @param tryCount  重试次数
     * @return 返回造成异常的数据刷新的观察者
     */
    protected abstract Observable<?> retryWhenObservable(Throwable throwable, int tryCount);

    /**
     * 处理自定义异常<br>
     * 比如:token失效异常等
     *
     * @param code 错误码
     * @param msg  错误提示
     */
    protected abstract void handleCustomizeException(int code, String msg, OnHttpListener listener);

    /**
     * 处理全局网络请求异常
     *
     * @param e        异常
     * @param listener 分发回调接口
     */
    protected abstract void handleErrorException(Throwable e, OnHttpListener listener);


    /**
     * 实际的api请求回调接口
     *
     * @param <T> {@link IResponseResult}类型的实体类才能触发异常重试机制
     */
    public interface OnObservableListener<T> {
        /**
         * 获取原始的Observable
         *
         * @return
         */
        public Observable<T> onObservable();
    }


    /**
     * 文件上传(异步)
     *
     * @param url      上传地址
     * @param params   上传参数
     * @param listener 回调监听
     * @return
     */
    public Call uploadFiles(String url, Map<String, Object> params, OnHttpListener<String> listener) {
        return OkUploadFiles.uploadFiles(buildOkhttpClient(), url, params, listener);
    }

    /**
     * 文件上传(同步)
     *
     * @param url      上传地址
     * @param params   上传参数
     * @param listener 回调监听
     * @return
     */
    public Call uploadFilesSync(String url, Map<String, Object> params, OnHttpListener<String> listener) {
        return OkUploadFiles.uploadFilesSync(buildOkhttpClient(), url, params, listener);
    }

    /**
     * 异步文件下载（支持进度条）(不支持异常重试)
     *
     * @param url      下载路径
     * @param listener 监听接口
     */
    public Call downloadFile(final String url, final OnHttpListener<Response> listener) {
        final Request request = new Request.Builder().url(url).build();
        Call call = buildOkhttpClient().newBuilder().addInterceptor(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Response originalResponse = chain.proceed(chain.request());
                //包装响应体并返回
                return originalResponse.newBuilder().body(new ProgressResponseBody(originalResponse.body(), new ProgressResponseBody.ProgressResponseListener() {
                    @Override
                    public void onResponseProgress(long totalBytesRead, long totalLength, int progress) {
                        if (listener != null) {
                            listener.onDownloadProgress(totalBytesRead, totalLength, progress);
                        }
                    }
                })).build();
            }
        }).build().newCall(request);
        //发起请求
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                if (listener != null) {
                    listener.onFailure(e.hashCode(), e.getMessage());
                }
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if (listener != null) {
                    if (response.isSuccessful()) {
                        listener.onSuccess(response);
                    } else {
                        listener.onFailure(response.code(), response.message());
                    }
                }
            }
        });
        return call;
    }


    /**
     * 获取错误码关键字(配合请求返回{@link Observable<okhttp3.ResponseBody>}时使用)
     *
     * @return
     */
    @android.support.annotation.NonNull
    protected abstract String getErrCodeKey();

    /**
     * 获取错误信息关键字(配合请求返回{@link Observable<okhttp3.ResponseBody>}时使用)
     *
     * @return
     */
    @android.support.annotation.NonNull
    protected abstract String getErrMsgKey();

}

